Skip to content

fix(types): give seven recursion-breaking zod mirrors their declaration as both type arguments - #8354

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-7760-lazy-mirror-input-type-args
Sep 8, 2026
Merged

fix(types): give seven recursion-breaking zod mirrors their declaration as both type arguments#8354
os-zhuang merged 3 commits into
mainfrom
claude/issue-7760-lazy-mirror-input-type-args

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #7760

Executes the maintainer ruling of decision batch #69 (2026-09-07, reply verbatim 「同意」): the ten z.lazy mirrors annotated z.ZodType<any> get their existing TypeScript declaration as both type arguments, so the published input face of every slot that reaches them carries the node union instead of unknown. The runtime accept set does not move; @objectstack/spec is untouched; the TS declarations are untouched.

Ruled 乙 with 丙 as the per-const fallback, and the trial below is why that clause was written.

The trial, one const at a time

Each row is its own run: the const's annotation filled on an otherwise clean origin/main tree, then tsc --noEmit over packages/types. Order-dependent refusals were re-run in the combined tree, and the verdict column is the combined result.

# const verdict evidence
1 base.zod.ts#SchemaNodeSchema 乙 — filled z.ZodType<SchemaNode, SchemaNode>
2 app.zod.ts#MenuItemSchema 乙 — filled z.ZodType<AppMenuItem, AppMenuItem>
3 crud.zod.ts#ActionSchema 乙 — filled z.ZodType<ActionDeclaration, ActionDeclaration> (the TS twin is also named ActionSchema, so it is imported under an alias)
4 data-display.zod.ts#TreeNodeSchema 乙 — filled z.ZodType<TreeNode, TreeNode>
5 navigation.zod.ts#NavLinkSchema 乙 — filled z.ZodType<NavLink, NavLink>
6 navigation.zod.ts#NavigationMenuItemSchema 乙 — filled z.ZodType<NavigationMenuItem, NavigationMenuItem>
7 overlay.zod.ts#MenuItemSchema 乙 — filled z.ZodType<MenuItem, MenuItem>
8 app.zod.ts#NavigationItemSchema keeps the annotation TS2322 — see below
9 complex.zod.ts#FilterBuilderConditionSchema keeps the annotation TS2322 — see below
10 complex.zod.ts#FilterGroupSchema keeps the annotation TS2322, transitive on 9

The three refusals, verbatim from tsc:

src/zod/app.zod.ts(136,14): error TS2322: Type 'ZodLazy<ZodObject<...>>' is not assignable to
  type 'ZodType<NavigationItem, NavigationItem, $ZodTypeInternals<NavigationItem, NavigationItem>>'.
  The types of '_output.id' are incompatible between these types.
    Type 'string | undefined' is not assignable to type 'string'.

src/zod/complex.zod.ts(291,14): error TS2322: Type 'ZodLazy<ZodObject<...>>' is not assignable to
  type 'ZodType<FilterBuilderCondition, FilterBuilderCondition, ...>'.
  The types of '_output.operator' are incompatible between these types.
    Type '"in" | ... | "is_null" | "is_not_null"' is not assignable to type 'FilterBuilderOperator'.
      Type '"is_null"' is not assignable to type 'FilterBuilderOperator'.

src/zod/complex.zod.ts(320,14): error TS2322: Type 'ZodLazy<ZodObject<...>>' is not assignable to
  type 'ZodType<FilterGroup, FilterGroup, ...>'.
  The types of '_input.conditions' are incompatible between these types.
    Type 'unknown[]' is not assignable to type '(FilterGroup | FilterBuilderCondition)[]'.

The ruling's stated cause for the fallback did not occur, and a different one did

The execution note conditions the 丙 fallback on the TS declaration being derived from the zod schema (z.infer / z.input), which would re-close the inference cycle. That did not happen on any of the ten. All ten declarations are hand-written interface / type (checked: no z.infer or z.input of a local mirror anywhere in the declaration files), and no run produced a circularity diagnostic.

What the three refusals are instead: the mirror already accepts more than its declaration states, so the assignment tsc performs to check the annotation is the wider-direction comparison, and it fails.

  • NavigationItemSchema — the mirror declares id optional (deliberately, so a bare { type: 'separator' } parses); NavigationItem.id is required.
  • FilterBuilderConditionSchema — the mirror's operator enum carries is_null / is_not_null; FilterBuilderOperator carries is_empty / is_not_empty instead.
  • FilterGroupSchema — its conditions union names FilterBuilderConditionSchema, so it follows the one above.

Measured, not assumed: filling only the INPUT position (z.ZodType<any, NavigationItem>) fails identically, on _input.id and _input.operator. Closing either would need a runtime accept-set change or a declaration change, both of which the ruling forbids — so these three keep the annotation and stay with 丙, which is where the ruling's fallback puts them. The trigger for that branch is wider than the ruling predicted, and this PR does not narrow it silently.

The parity ledger can now see the region it had excluded

packages/types/src/__tests__/zod-mirror-parity.test.ts — the ledger from #7069 / PR #7758 — excluded every slot whose input face was unknown, which is exactly the z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]) single-or-list producer that card called systematic. Re-run on this branch:

ledger before (5505aec) after (a26c3f9)
WiderThanDeclared entries 34 23
WiderThanDeclared keys 52 36
WIDER_ARMS arms 61 47
— of which SCHEMA-NODE keys 24 6
— of which CONCRETE keys 27 30
— of which MIXED keys 1 0
— union-arity keys 9 11
KnownDrift entries / keys 41 / 63 42 / 64
UnmirroredDeclared + RuntimeOnlyDeclared 15 pairs / 111 keys unchanged

The "before" column is derived on this PR's base commit by the same instrument, not quoted from any card — and it reproduces the header's own figures exactly, which is what makes the "after" column readable.

19 keys across 16 pairs left (13 entries emptied): the reading they recorded was the annotation, not an accept-set gap. 3 keys entered, all three real widenings the erased face had been hiding, all three the single-or-list spelling:

  • feedback.zod.ts#ToastSchema::action — mirror SchemaNode | SchemaNode[], declaration { label: string; onClick: () => void }. DISJOINT, so it enters KnownDrift too. Note the declaration is the suspect face: ToastSchema.onDismiss sits one member below it as a finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 tombstone telling authors to "author behaviour as a node type", which is what the mirror already accepts.
  • navigation.zod.ts#HeaderBarSchema::logo — mirror SchemaNode | SchemaNode[], declaration logo?: string.
  • overlay.zod.ts#TooltipSchema::content — mirror SchemaNode | SchemaNode[], declaration content?: string | SchemaNode: the same union without the list arm, while the sibling body on the same pair declares the list and agrees.

None of the three is repaired here — the ruling's scope is the type argument. They are ledgered, with both faces named, for 丙's lane.

The runtime walk leg is unchanged and still pins the region's sources; its docblock now says why the list keeps all ten names even though seven no longer produce a blind face (it detects z.lazy nodes, which is what makes an eleventh visible).

assertionSchemaNodeFaceIsUnconstrained fired on the first compile, exactly as its own note said it would. It is kept, inverted, and a second line was added on NavigationItemSchema so the exclusion's remaining members are pinned from the other side too — a revert of either annotation is red rather than silently unmeasured.

Verification

  • pnpm --filter @object-ui/types type-check — exit 0 (three projects: tsc --noEmit, tsconfig.examples.json, tsconfig.test.json; the ledger's ratchets are compile-time assertions read by the third).
  • vitest run --project unit packages/types/src/__tests__141 files, 2695 tests passed on the merged head.
  • vitest run --project unit (whole repo, pre-merge head) — 960 files, 16230 passed, 2 skipped.
  • turbo run build --concurrency=2 --filter='!@object-ui/site'43/43 successful.
  • turbo run type-check --concurrency=2 --filter='...@object-ui/types'77/77 successful. Direction declared: ...<pkg> is the package and its dependents, i.e. every downstream consumer of the changed published types.
  • Gates, each run with the exit code captured before any pipe: check:element-data-source-declaration · check:sdui-registration-pins · check:spec-symbols · check:spec-floors · check:doc-types · check:doc-snippets · check:doc-fences · check:readme-exports · check:control-bytes · check:handler-key-reads · check:self-import · check:phantom-deps — all exit 0.
  • Lint: pnpm --filter @object-ui/types lint exit 0 (206 files linted, 0 errors, 266 pre-existing no-explicit-any warnings — seven fewer than before, since seven z.ZodType<any> annotations are gone) and pnpm run lint:root exit 0. Narrowed deliberately: the diff touches one package plus .changeset/, and this repo's lint gate is per-package (turbo run lint), so the other 43 packages have no changed file; eslint.config.js enables no type-aware linting (no project / projectService), so nothing in this diff can move a verdict on a file it does not contain.

Reverse verification (the published face really did narrow, read from the built .d.ts)

A consumer-side probe resolving @object-ui/types/zod through the package exports — so through dist, not the source alias — assigning a function value into z.input<typeof AppMenuItemSchema>:

leg dist/zod/app.zod.d.ts blob tsc
this branch cbfb6b4 exit 2TS2559: Type '() => undefined' has no properties in common with type 'AppMenuItem'
annotation ablated back to z.ZodType<any>, package rebuilt f03cc75 exit 0

The two dist blob hashes differ, so the second leg is a real rebuild and not a cache read. The ablation and probe were removed afterwards; git status and git diff HEAD are both empty and the dist blob is back to cbfb6b4.

Declared deviation

packages/types/src/__tests__/app-action-onclick-refusal-6854.test.ts is outside the declared file surface and carries a one-line consequential edit. AppActionSchema.items used to infer as any[] because its element mirror was the erased MenuItemSchema; it is AppMenuItem[] now, so a direct type assertion to { items: Record<string, unknown>[] } no longer overlaps and tsc refuses it (TS2352). The repair routes that one assertion through unknown and adds the reason. No assertion in the file changed, and the file's subject — that both undeclared keys are gone from the parsed value at runtime — is untouched. Reported rather than absorbed: without it the ruled change cannot compile at all.

Serial constraints

  • #7918 (same ten consts, different change) is CLOSED, completed by merged PR fix(types): memoise the two zod lazy getters that can be, pin why the other eight cannot (objectui#7918) #8226 (4f9f1ee, which memoised two of the lazy getters). This PR touches no getter — verified at contract review. ⚠️ The earlier text here said it "rebases after this lands"; that is stale and is corrected rather than left. ⛔ It also spelled a delivery-declaration keyword immediately beside another card's number, in an accounting sentence — which makes prDeliversCard report a delivery that does not exist and manufactures a phantom carrier-split row (objectstack#16706, measured with a two-sided control). Respelled here with zero verbs beside the card number.
  • #8324 and #8330 consume SchemaNodeSchema without editing any file on this surface. origin/main was merged in at 411a132 after both had landed, pnpm install re-run, and the full packages/types type-check and test suite re-run green on the merge commit — including the ledger's ratchets against #7105's new EmptySchema.action: SchemaNode declaration.
  • #7759 is deferred behind this card; the parity ledger was held serially for exactly this reason.

Review

packages/types is not a governed surface (triage's correction, upheld by the ruling): ordinary merge queue. But this is a published type-face change, so clause ② applies: needs:contract-review is on the card and on this PR, the PR stays draft, and auto-merge is not armed. Only a CONTRACT_REVIEW_TIER reviewer clears the two carriers.


Generated by Claude Code


Generated by Claude Code

…on as both type arguments

The ten `z.lazy` mirrors annotated `z.ZodType<any>` publish `unknown` as their
static INPUT face, because zod 4 defaults that parameter when the argument is
`any`. Every slot spelled through one of them therefore told an author nothing:
`unknown` is wider than every declaration by definition and silent about what
the mirror accepts at runtime, where the lazy union does validate.

Seven now carry their existing TypeScript declaration in both positions —
`SchemaNodeSchema`, `zod/app`'s `MenuItemSchema`, `ActionSchema`,
`TreeNodeSchema`, `NavLinkSchema`, `NavigationMenuItemSchema` and `zod/overlay`'s
`MenuItemSchema`. The `z.lazy` bodies, the TS declarations and `@objectstack/spec`
are untouched, so no runtime accept set moves.

Three refused the argument, each measured one const at a time:
`NavigationItemSchema` (`_output.id`: `string | undefined` against a required
`string`), `FilterBuilderConditionSchema` (`_output.operator`: `is_null` /
`is_not_null` against `FilterBuilderOperator`) and, transitively through its
`conditions` arm, `FilterGroupSchema`. Each is a mirror that already accepts
more than its declaration states, so filling the argument IS that comparison and
`tsc` refuses the assignment. None of the ten re-closed an inference cycle.

The parity ledger (objectui#7069, PR #7758) can now see the region it had
declared EXCLUDED: `WiderThanDeclared` goes 34 entries / 52 keys / 61 arms to
23 / 36 / 47, its SCHEMA-NODE class 24 keys to 6, and three real widenings the
erased face had been hiding enter it — `ToastSchema.action`,
`HeaderBarSchema.logo` and `TooltipSchema.content`, all three the
`z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])` single-or-list spelling
that card called systematic. `KnownDrift` gains `ToastSchema.action` from the
other side. The runtime walk leg is unchanged and still bounds the sources.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.2 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-BtFtjBF1.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.55KB 114.03KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 193.79KB 46.09KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.56KB 63.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.38KB 15.72KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

This was referenced Sep 7, 2026
…ad/cast vector

Contract review at CONTRACT_REVIEW_TIER returned FAIL on the changeset only; the
code increment was judged sound and is untouched by this commit.

Two reproducible factual defects, both fixed here:

1. The prior output face was misstated, so the breakage vector was under-reported.
   `z.ZodType<any>` resolves to `ZodType<any, unknown>` (zod 4.4.3,
   `v4/classic/schemas.d.ts`: `interface ZodType<out Output = unknown,
   out Input = unknown, ...>`). So `z.input` read `unknown` — the changeset had
   that right — but `z.infer` / `z.output` read `any`, not `unknown`. The stated
   breakage mode ("code that assigned an arbitrary value into such a slot")
   therefore covered writes only; `any` to a declaration also breaks reads and
   casts of parsed output. This PR hit exactly that vector in-repo (the TS2352 on
   `app-action-onclick-refusal-6854.test.ts`), so external consumers of
   `@object-ui/types/zod` were being told the lesser vector. Both vectors are now
   named, with this PR's own TS2352 as the worked instance.

2. "Every package ... was rebuilt and type-checked against the change with no site
   needing a repair" contradicted the PR body's own declared deviation, which
   names one in-repo site that did need a repair. Corrected to match what happened.

No schema, test or code change: `.changeset/*.md` only.

Gates, exit captured before any pipe:
  node scripts/check-changeset-presence.mjs — exit 0
  node scripts/check-changeset-no-major.mjs — exit 0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.5 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-DzK6MxpQ.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.55KB 114.03KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.52KB 46.34KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.68KB 63.94KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.38KB 15.72KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-zhuang
os-zhuang marked this pull request as ready for review September 8, 2026 00:32
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit a480f79 Sep 8, 2026
34 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-7760-lazy-mirror-input-type-args branch September 8, 2026 00:48
os-sales added a commit that referenced this pull request Sep 8, 2026
… amend #8222's stale live-figure guidance

objectui#8458 — `zod-mirror-parity.test.ts`'s header said the objectui#7760 move
took "19 keys across 16 pairs". Every other figure in that paragraph is read by a
`headerFigures` pin and moved correctly with PR #8354; the pairs figure was the one
no spelling reached, so it compiled and tested green while wrong.

Re-derived rather than adopted: this file's own `ledgerEntryMembers('WiderThanDeclared')`
was sliced out of the source, transpiled and run over the file at `645ecb98c` and
`a480f797a` (PR #8354's parent and its merge), diffing entry -> key pairs. 19 keys
left and 3 entered, across 18 distinct entries — 13 emptied, 5 reduced. Both
movements close against figures already pinned at those revisions (52 - 19 + 3 = 36
keys; 34 - 13 + 2 = 23 entries). `16` is neither reading.

The figure is now pinned, following the precedent of the "N of the registered pairs
carry TYPE drift TODAY" spelling already in this file, and "pairs" is defined where
it stands: every entry the move TOUCHED, with `emptied` as the subset that lost its
whole content. The pin reads only the sentence — the movement is a HISTORICAL
reading of two fixed trees, so nothing compares it to the live ledger.

objectui#8248 — objectui#8222's exclusion note still called the seed decomposition a
LIVE figure, which objectui#8243 made historical. Per the maintainer ruling (decision
batch #71) the guidance clause is amended and the two measurement-record clauses are
untouched; the record-vs-guidance rule is written once, in the file header.

Test-only: the file is under `__tests__/`, excluded by the package build tsconfig
and absent from its published `files`. Declared with an empty-frontmatter changeset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZY49skxUBYyJcdnTcYPrE
os-sales added a commit that referenced this pull request Sep 8, 2026
… amend #8222's stale live-figure guidance

objectui#8458 — `zod-mirror-parity.test.ts`'s header said the objectui#7760 move
took "19 keys across 16 pairs". Every other figure in that paragraph is read by a
`headerFigures` pin and moved correctly with PR #8354; the pairs figure was the one
no spelling reached, so it compiled and tested green while wrong.

Re-derived rather than adopted: this file's own `ledgerEntryMembers('WiderThanDeclared')`
was sliced out of the source, transpiled and run over the file at `645ecb98c` and
`a480f797a` (PR #8354's parent and its merge), diffing entry -> key pairs. 19 keys
left and 3 entered, across 18 distinct entries — 13 emptied, 5 reduced. Both
movements close against figures already pinned at those revisions (52 - 19 + 3 = 36
keys; 34 - 13 + 2 = 23 entries). `16` is neither reading.

The figure is now pinned, following the precedent of the "N of the registered pairs
carry TYPE drift TODAY" spelling already in this file, and "pairs" is defined where
it stands: every entry the move TOUCHED, with `emptied` as the subset that lost its
whole content. The pin reads only the sentence — the movement is a HISTORICAL
reading of two fixed trees, so nothing compares it to the live ledger.

objectui#8248 — objectui#8222's exclusion note still called the seed decomposition a
LIVE figure, which objectui#8243 made historical. Per the maintainer ruling (decision
batch #71) the guidance clause is amended and the two measurement-record clauses are
untouched; the record-vs-guidance rule is written once, in the file header.

Test-only: the file is under `__tests__/`, excluded by the package build tsconfig
and absent from its published `files`. Declared with an empty-frontmatter changeset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CZY49skxUBYyJcdnTcYPrE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants